//---------------------------------------------------------------------
// Authors: jachymko
//
// Description: Base class for handlers which expand expressions.
//
// Creation Date: Jan 4, 2007
//
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Text;

using BGShell.PowerShell.Commands;

namespace BGShell.PowerShell.TabExpansion.Handlers
{
    internal abstract class ExpressionHandlerBase : TabExpansionHandler
    {
        private static readonly string[] AdditionalMembers = new string[] 
        {
            "PSBase", "PSObject"
        };

        protected ExpressionHandlerBase(GetTabExpansionCommand cmd)
            : base(cmd)
        {
        }
        protected static string[] SplitDots(string expression)
        {
            return expression.Split('.');
        }

        protected static string[] SplitDots(string expression, out string lastChild)
        {
            lastChild = string.Empty;

            string[] children = SplitDots(expression);
            int count = children.Length;

            if (count > 0)
            {
                string[] newChildren = new string[count - 1];
                Array.Copy(children, newChildren, count - 1);

                lastChild = children[count - 1];
                children = newChildren;
            }

            return children;
        }

        protected static string BuildInvokeExpression(string parent, string[] children)
        {
            int count = children.Length - 1;

            StringBuilder script = new StringBuilder();
            script.Append(parent);

            if (count > 0)
            {
                script.Append('.');
            }

            for (int i = 0; i < count; ++i)
            {
                script.Append(children[i]);

                if (i < count - 1)
                {
                    script.Append('.');
                }
            }

            return script.ToString();
        }

        protected static string BuildStaticExpression(string typeName, string[] children)
        {
            int count = children.Length;

            StringBuilder script = new StringBuilder();
            script.Append('[');
            script.Append(typeName);
            script.Append("]::");

            for (int i = 0; i < count; ++i)
            {
                script.Append(children[i]);

                if (i < count - 1)
                {
                    script.Append('.');
                }
            }

            return script.ToString();
        }

        protected void ExpandExpression(string parent, string expression)
        {
            ExpandExpression(parent, expression, null);
        }

        protected void ExpandExpression(string parent, string expression, string resultPrefix)
        {
            string[] children = SplitDots(expression);
            string left = BuildInvokeExpression(parent, children);

            Collection<PSObject> results = Invoke(left);
            if(results != null)
            {
                if (results.Count == 1)
                {
                    ExpandObjectMembers(results[0], left, children, resultPrefix);
                }
                else if (results.Count > 1)
                {
                    ExpandObjectMembers(new PSObject(results), left, children, resultPrefix);
                }
            }
        }

        protected void ExpandObjectMembers(PSObject obj, string left, string[] children, string resultPrefix)
        {
            string namePrefix = children[children.Length - 1];
            
            if (resultPrefix == null)
            {
                resultPrefix = left + '.';
            }

            WriteExpansions(GetMemberNames(obj, namePrefix, resultPrefix));
        }

        protected IEnumerable<String> GetMemberNames(PSObject obj, string namePrefix, string resultPrefix)
        {
            foreach (PSMemberInfo mi in obj.Members)
            {
                if (ShouldShowMember(mi.Name, namePrefix))
                {
                    yield return resultPrefix + MemberToString(mi);
                }
            }

            foreach (string additional in AdditionalMembers)
            {
                if (ShouldShowMember(additional, namePrefix))
                {
                    yield return resultPrefix + additional;
                }
            }
        }

        protected IEnumerable<String> GetMemberNames(IEnumerable<PSObject> memberInfos, string resultPrefix, string namePrefix)
        {
            foreach (PSObject member in memberInfos)
            {
                string memberName = (string)member.Properties["Name"].Value;
                PSMemberTypes memberType = (PSMemberTypes)member.Properties["MemberType"].Value;

                if (string.IsNullOrEmpty(memberName))
                {
                    continue;
                }

                if (ShouldShowMember(memberName, namePrefix))
                {
                    yield return resultPrefix + MemberToString(memberType, memberName);
                }
            }
        }

        //protected bool ConfirmInvocation(string expression)
        //{
        //    string message = string.Format(Resources.ConfirmExpressionInvocation, expression);

        //    Coordinates originalCursorPos = Command.Host.UI.RawUI.CursorPosition;

        //    Command.Host.UI.WriteLine();
        //    Command.Host.UI.WriteLine(message);

        //    KeyInfo key = Command.Host.UI.RawUI.ReadKey(ReadKeyOptions.IncludeKeyUp | ReadKeyOptions.NoEcho);

        //    int numberOfLines = Command.Host.UI.RawUI.CursorPosition.Y - originalCursorPos.Y;

        //    Command.Host.UI.RawUI.CursorPosition = originalCursorPos;

        //    Command.Host.UI.WriteLine();
        //    Command.Host.UI.WriteLine(new string(' ', numberOfLines * Command.Host.UI.RawUI.BufferSize.Width));

        //    return (key.Character == '\t');
        //}

        private static string MemberToString(PSMemberInfo mi)
        {
            PSMethodInfo methodInfo = mi as PSMethodInfo;
            if (methodInfo != null)
            {
                Collection<String> overloads = methodInfo.OverloadDefinitions;

                // HACK! If you know a better way to find out whether the 
                // method needs parameters, fix this please.

                if (overloads.Count == 1 && overloads[0].EndsWith("()", StringComparison.Ordinal))
                {
                    return mi.Name + "()";
                }
            }

            return MemberToString(mi.MemberType, mi.Name);
        }

        private static string MemberToString(PSMemberTypes type, string name)
        {
            if (IsMethod(type))
            {
                return name + '(';
            }

            return name;
        }

        private static bool ShouldShowMember(string name, string namePrefix)
        {
            const string Getter = "get_";
            const string Setter = "set_";

            if (StartsWith(name, Getter) && !StartsWith(namePrefix, Getter))
            {
                return false;
            }

            if (StartsWith(name, Setter) && !StartsWith(namePrefix, Setter))
            {
                return false;
            }

            return string.IsNullOrEmpty(namePrefix) || StartsWith(name, namePrefix);
        }

        private static bool StartsWith(string str, string prefix)
        {
            return str.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
        }
    }
}
